# -*- coding: utf-8 -*-
import urllib
import httplib
import socket
import base64
import re

from estraierpure.exceptions import ResponseError
from estraierpure.document import Document
from estraierpure.search import ResultSet, MetaData, parse_draft

class Transport(object):
    def __init__(self, host, port, path, user, password):
        self.host = host
        self.port = port
        self.path = path
        if user:
            self.auth = 'Basic %s' % base64.b64encode("%s:%s" % (user, password or ''))
        else:
            self.auth = None

    def send(self, command, data=None, estrairer_draft=False):
        try:
            conn = httplib.HTTPConnection(self.host, self.port)
            conn.connect()
            
            if data:
                method = 'POST'
            else:
                method = 'GET'
            
            conn.putrequest(method, self.path + command)
            
            if data:
                conn.putheader('Content-Length', len(data))
                if estrairer_draft:
                    conn.putheader('Content-Type', 'text/x-estraier-draft')
                else:
                    conn.putheader('Content-Type', 'application/x-www-form-urlencoded')
            
            if self.auth:
                conn.putheader('Authorization', self.auth)
            conn.endheaders()
            
            conn.send(data or '')
            
            r = conn.getresponse()
            conn.close()
            if r.status == 401 or r.status == 404:
                raise ResponseError(r.status, r.reason)

            return r
        except socket.error, (code, reason):
            raise ResponseError(code, reason)

class Node(object):
    def __init__(self, name, user=None, password=None, host=None, port=1978):
        self.__name = name
        self.conn = Transport(host or 'localhost', port, '/node/%s/' % name, user, password)

    def send_request(self, command, params = None):
        if params:
            params = urllib.urlencode(params)

        return self.conn.send(command, params)

    def get_name(self):
        return self.__name
    name = property(get_name, None, None, "the node name")

    def put_doc(self, doc):
        if not isinstance(doc, Document):
            raise ValueError("Invalid argument for put_doc: %r" % doc)
    
        res = self.conn.send('put_doc', doc.dump_draft(), True)
        return res.status == 200

    def edit_doc(self, doc):
        if not isinstance(doc, Document):
            raise ValueError("Invalid argument for edit_doc: %r" % doc)

        res = self.conn.send('edit_doc', doc.dump_draft(), True)
        return res.status == 200

    def get_doc(self, id):
        res = self.send_request('get_doc', {'id':id})
        doc = parse_draft(res)
        res.close()
        return doc

    def get_doc_by_uri(self, uri):
        res = self.send_request('get_doc', {'uri':id})
        doc = parse_draft(res)
        res.close()
        return doc

    def get_doc_attr(self, id, name):
        """
        Retrieve the value of an attribute of a document.
        """
        res = self.send_request('get_doc_attr', {'id':id, 'attr':name})
        return unicode(res.read(), 'utf-8')

    def get_doc_attr_by_uri(self, uri, name):
        """
        Retrieve the value of an attribute of a document specified by URI.
        """
        res = self.send_request('get_doc_attr', {'uri':uri, 'attr':name})
        return unicode(res.read(), 'utf-8')

    def get_doc_keywords(self, id=None, uri=None):
        if uri is not None:
            params = { 'uri': uri }
        elif id is not None:
            params = { 'id' : id }
        else:
            raise TypeError('Neither id nor uri is specified.')
    
        res = self.send_request('etch_doc', params)
        keywords = {}
        while 1:
            line = res.fp.readline()
            line = line[:-1]
            if not line:
                break

            k, v = line.split('\t', 1)
            keywords[unicode(k, 'utf-8')] = int(v)
        return keywords

    def get_doc_keywords_by_uri(self, uri):
        return self.get_doc_keywords(uri=uri)

    def search(self, phrase=None, attrs=None, order=None, options=None, auxiliary=None,
               distinct=None, depth=None, masks=None, wwidth=None, hwidth=None, awidth=None):
        query = { 'max': 0 }

        if phrase is not None:
            query['phrase'] = phrase

        if attrs is not None:
            for i, a in enumerate(attrs):
                query['attr%d' % (i+1)] = a

        if isinstance(masks, (list, tuple)):
            for m in masks:
                query['mask%d' % m] = 'on'
        elif masks is not None:
            query['masks'] = masks

        if order is not None:
            query['order'] = order
        if options is not None:
            query['options'] = options
        if auxiliary is not None:
            query['auxiliary'] = auxiliary
        if distinct is not None:
            query['distinct'] = distinct
        if depth is not None: 
            query['depth'] = depth
        if wwidth is not None: 
            query['wwidth'] = wwidth
        if hwidth is not None: 
            query['hwidth'] = hwidth
        if awidth is not None: 
            query['awidth'] = awidth

        res = self.send_request('search', query)
        try:
            rawdata = res.read()
            hit = int(re.search(r'HIT\s(\d+)', rawdata).group(1))
            lines = rawdata.split('\n')

            rs = ResultSet(self, query, hit)
            rs._metadata = MetaData(lines[1:-2])
            return rs
        except (AttributeError, ValueError):
            # invalid response from the hyper estraier node.
            raise

    def out_doc(self, id):
        """
        Remove a document.
        """
        res = self.send_request('out_doc', {'id': id})
        return res.status == 200

    def out_doc_by_uri(self, uri):
        """
        Remove a document specified by URI.
        """
        res = self.send_request('out_doc', {'uri': uri})
        return res.status == 200

    def uri_to_id(self, uri):
        """
        Get the ID of a document specified by URI.
        """
        res = self.send_request('uri_to_id', {'uri':uri})
        return res.read()

    def cache_usage(self):
        """
        Get the usage ratio of the cache.
        """
        res = self.conn.send('cacheusage')
        usage = float(res.read().strip())
        return usage

    def info(self):
        res = self.conn.send('inform')
        data = res.read().strip().split('\t')
        return data

    def sync(self):
        """
        Synchronize updating contents of the database.
        """
        res = self.conn.send('sync')
        return res.status == 200

    def optimize(self):
        """
        Optimize the database.
        """
        res = self.conn.send('optimize')
        return res.status == 200

class NodeMaster(object):
    def __init__(self, user=None, password=None, host=None, port=1978):
        self.master = Transport(host or 'localhost', port, '/master?action=', user, password)

    def shutdown(self):
        r = self.master.send('shutdown')
        return r.status <= 202

    def syncall(self):
        r = self.master.send('sync')
        return r.status <= 202

    def backup(self):
        r = self.master.send('backup')
        return r.status <= 202

    def logrtt(self):
        r = self.master.send('logrtt')
        return r.status <= 202

    def users(self):
        r = self.master.send('userlist')
        if r.status <= 202:
            buf = []
            for line in r.read().split('\n')[:-1]:
                buf.append(line.split('\t'))
            return buf
        return False

    def nodes(self):
        r = self.master.send('nodelist')
        if r.status <= 202:
            buf = []
            for line in r.read().split('\n')[:-1]:
                buf.append(line.split('\t'))
            return buf
        return False
